[備忘録] 同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価する

[備忘録] 同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価する

Clock Icon2023.03.02

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 Delivery部の若槻です。

今回は、同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価することがよくあるのですが、その際のテストの記述をよく忘れてはググっているので、備忘としてまとめておきます。

先にまとめ

同期関数/非同期関数の例外のスローは、それぞれ次の記述でテストできます。

test('_syncFunc', () => {
  //同期関数のテスト
  expect(() => syncFunc()).toThrow(new Error('syncFunc failed.'));
});

test('_asyncFunc', async () => {
  //非同期関数のテスト
  await expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));
});

解説

Jestで呼び出された関数が例外をスローしたことを評価したい場合は、.toThrow(error?)を使用します。

Use .toThrow to test that a function throws when it is called.

また、JavaScriptの非同期関数は実行結果としてPromiseを返します。

Promiseがreject(例外をスロー)することを期待したテストを実施したい場合、Jestではrejectsマッチャーを使用します。

If you expect a promise to be rejected, use the .rejects matcher. It works analogically to the .resolves matcher. If the promise is fulfilled, the test will automatically fail.

検証

次の関数で例外がスローされていることをテストしたいとします。

export const syncFunc = () => {
  subFunc();
  throw new Error('syncFunc failed.');
};

export const asyncFunc = async () => {
  subFunc();
  throw new Error('asyncFunc failed.');
};

export const subFunc = () => {
  console.log('hoge');
};

正しいパターン

まず、冒頭で示した、正しいパターンの記述によりテストが正常にPASSするパターンです。

import { syncFunc, asyncFunc, subFunc } from '../func';

jest.setTimeout(10000);

test('_syncFunc', () => {
  (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);

  //同期関数のテスト - 正しいパターン
  expect(() => syncFunc()).toThrow(new Error('syncFunc failed.'));

  expect(subFunc).toBeCalledTimes(1);
});

test('_asyncFunc', async () => {
  (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);

  //非同期関数のテスト - 正しいパターン
  await expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));

  expect(subFunc).toBeCalledTimes(1);
});

いずれのテストもPASSします。

$ npx jest              
 PASS  test/func.test.ts (5.387 s)
  ✓ _syncFunc (5 ms)
  ✓ _asyncFunc (3002 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.444 s
Ran all test suites.

正しくないパターン

次に、よくやりがちな、正しくないパターンの記述によりテストがFAILするパターンです。

import { syncFunc, asyncFunc, subFunc } from '../func';

jest.setTimeout(10000);

test('_syncFunc', () => {
  (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);

  //同期関数のテスト - 正くないパターン(無名関数となっていない)
  expect(syncFunc()).toThrow(new Error('syncFunc failed.'));

  expect(subFunc).toBeCalledTimes(1);
});

test('_asyncFunc', async () => {
  (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0);

  //非同期関数のテスト - 正しくないパターン(expectが同期呼び出しされていない)
  expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));

  expect(subFunc).toBeCalledTimes(1);
});

同期関数のテストを実行すると失敗しました。syncFuncがスローした例外がマッチャー側でキャッチできず関数の実行自体がFAILしています。

$ npx jest -t "_syncFunc"
 FAIL  test/func.test.ts
  ✕ _syncFunc (1 ms)
  ○ skipped _asyncFunc

  ● _syncFunc

    syncFunc failed.

      1 | export const syncFunc = () => {
      2 |   subFunc();
    > 3 |   throw new Error('syncFunc failed.');
        |         ^
      4 | };
      5 |
      6 | export const asyncFunc = async () => {

      at syncFunc (func.ts:3:9)
      at Object.<anonymous> (test/func.test.ts:9:18)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 skipped, 2 total
Snapshots:   0 total
Time:        2.248 s, estimated 3 s
Ran all test suites with tests matching "_syncFunc".

非同期関数のテストの実行も失敗しました。asyncFuncの同期呼び出しが完了する前にsubFuncの呼び出しが評価されており、テストがFAILしています。また非同期呼び出しされた関数の実行が完了する前にJestのテスト実行が終了しているため、Jest did not exit one second after the test run has completed.というWarningが出ています。

$ npx jest -t "_asyncFunc" 
 FAIL  test/func.test.ts
  ✕ _asyncFunc (2 ms)
  ○ skipped _syncFunc

  ● _asyncFunc

    expect(jest.fn()).toBeCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

      18 |   expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.'));
      19 |
    > 20 |   expect(subFunc).toBeCalledTimes(1);
         |                   ^
      21 | });
      22 |

      at Object.<anonymous> (test/func.test.ts:20:19)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 skipped, 2 total
Snapshots:   0 total
Time:        2.182 s, estimated 3 s
Ran all test suites with tests matching "_asyncFunc".
Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

参考

以上

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.